package com.jule.teenpatti.game.play;

import JoloProtobuf.GameSvr.JoloGame;
import com.alibaba.fastjson.JSONObject;
import com.google.protobuf.MessageLite;
import com.jule.core.common.log.LoggerUtils;
import com.jule.core.jedis.StoredObjManager;
import com.jule.core.utils.fifo.FIFORunnableQueue;
import com.jule.teenpatti.base.dao.bean.User;
import com.jule.teenpatti.base.enums.*;
import com.jule.teenpatti.base.model.GameRoomTableSeatRelationModel;
import com.jule.teenpatti.base.model.RoomTableRelationModel;
import com.jule.teenpatti.game.config.Config;
import com.jule.teenpatti.game.dao.DBUtil;
import com.jule.teenpatti.game.dao.bean.CommonConfigModel;
import com.jule.teenpatti.game.dao.bean.RoomConfigModel;
import com.jule.teenpatti.game.gameUtil.GameLogic;
import com.jule.teenpatti.game.model.CardConstent;
import com.jule.teenpatti.game.model.HandCardTypeModel;
import com.jule.teenpatti.game.model.PlayerInfo;
import com.jule.teenpatti.game.model.eenum.NextBetPlayerReasonEnum;
import com.jule.teenpatti.game.model.eenum.PlayTypeEnum;
import com.jule.teenpatti.game.model.eenum.PlayerStateEnum;
import com.jule.teenpatti.game.network.protocol.ClientReq;
import com.jule.teenpatti.game.network.protocol.logic.LeaveTableLogic;
import com.jule.teenpatti.game.network.protocol.protoutil.JoloGame_tablePlay_OtherPlayerInfoBuilder;
import com.jule.teenpatti.game.service.*;
import com.jule.teenpatti.game.service.holder.CardOfTableHolder;
import com.jule.teenpatti.game.service.holder.CommonConfigHolder;
import com.jule.teenpatti.game.service.holder.FunctionIdHolder;
import com.jule.teenpatti.game.service.holder.RoomConfigHolder;
import com.jule.teenpatti.game.utils.log.TableLogUtil;
import com.jule.teenpatti.game.vavle.notice.NoticeBroadcastMessages;
import com.jule.teenpatti.game.vavle.notice.NoticeRPCUtil;
import com.jule.teenpatti.log.service.LogReasons;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;

@Setter
@Getter
public class AbstractTable implements ITable {
    protected final static Logger log = LoggerFactory.getLogger(AbstractTable.class);

    private final FIFORunnableQueue fifoRunnableQueue = new FIFORunnableQueue<ClientReq>() {
    };
    /**
     * 玩法类型
     */
    public int playType;
    private String tableId;
    private String roomId;
    private RoomTableRelationModel roomTableRelation;
    private boolean propertyChange = false;
    /**
     * 每次开局前重新加载
     */
    private CommonConfigModel commonConfig = null;
    private RoomConfigModel roomConfig = null;
    /**
     * 当前庄家座位号
     */
    private int currDealerSeatNum;
    /**
     * 基础下注积分
     */
    private long baseBetScore;
    //房间所有人 key：userId
    private final Map<String, PlayerInfo> allPlayers = new ConcurrentHashMap<>();
    /*
     * 已坐下的玩家列表<位置，playerInfo>
     * key：seatNum
     */
    private final Map<Integer, PlayerInfo> inGamePlayersBySeatNum = new ConcurrentHashMap<>();
    /*
     * 已坐下的玩家列表<userId,PlayerInfo>
     * key：userId
     */
    private final Map<String, PlayerInfo> inGamePlayersByPlayerId = new ConcurrentHashMap<>();
    /*
     * 游戏中的人(弃牌就移出)
     * key：seatNum
     */
    private final Map<Integer, PlayerInfo> inGamePlayers = new ConcurrentHashMap<>();
    /*
     * 玩家每局游戏的下注额，结算清空(统计日志)
     * key：playerId
     */
    private final Map<String, Double> alreadyBet = new ConcurrentHashMap<>();
    /**
     * 牌桌目前总的下注金额
     */
    private double totalAlreadyBet;
    /**
     * 当前轮次ID（轮次ID从1开始累加增长）
     */
    protected int currRoundId = 1;
    private long maxPotScore; //每局牌局所允许进行的最大底池积分数
    private String currGameOrderId = ""; //当前牌局单号
    private int mixedCardId; //本局的癞子牌
    protected int dealerPlayType = 0;//dealer当前的玩法
    /**
     * 当前明牌基础下注值 暗牌需除以2
     */
    private long currBaseBetScore;
    //死机玩家的id 用于强制提出
    private int offlineSeatNum;
    private String offlinePlayerId;
    private TableStateEnum tableStateEnum; //牌桌状态
    protected int firstBetSeatNum; //本局第一个下注玩家座位号
    private int seeCardNum; //桌子上已看牌的人数
    private String currActionPlayerId; //当前行动玩家Id
    private int currBetSeqId; //当前下注消息序号（1-n）
    protected List<Integer> changeCards = new ArrayList<>(); //change出去的牌

    //当前行动玩家座位号(需要在换桌、站起时进行处理)
    private int currActionSeatNum;
    private Set<String> openCardPlayerIds = new HashSet<>(2);
    private String currDoSideShowPlayerId; //当前比牌发起用户ID
    private String currTargetSideShowPlayerId; //当前被比牌用户ID
    private ConcurrentLinkedQueue<Integer> nullSeatList = new ConcurrentLinkedQueue<>(); //可用的空座位列表
    private List<String> loseUserIds = new ArrayList<>();
    //上次本桌操作时间
    private long lastActionTime = 0;
    private List<PlayerInfo> fullPressure = new ArrayList<>();
    private Map<String,int[]> playerCardsMap = new ConcurrentHashMap<>(); //玩家手牌


    public AbstractTable(String gameId, String roomId, String tableId) {
        setPlayType(Integer.parseInt(gameId));
        setRoomId(roomId);
        setTableId(tableId);
        roomTableRelation = new RoomTableRelationModel(gameId, roomId, tableId,TableStateEnum.IDEL.getValue());

        //初始化空座位
        addNullSeat(1);
        addNullSeat(2);
        addNullSeat(3);
        addNullSeat(4);
        addNullSeat(5);

        setCommonConfig(CommonConfigHolder.getInstance().getCommonConfig(Integer.parseInt(gameId)));
        setRoomConfig(RoomConfigHolder.getInstance().getRoomConfig(roomId));
        initTableStateAttribute();

        tableStateEnum = TableStateEnum.IDEL;
        setTableStatus();
    }

    @Override
    public void joinTable(PlayerInfo player) {
        if (!allPlayers.containsKey(player.getPlayerId())) {
            UserTableService.getInstance().onPlayerInTable(getPlayType() + "", player, this);
            player.setState(PlayerStateEnum.spectator);
            allPlayers.put(player.getPlayerId(), player);
        }
    }

    @Override
    public boolean sitDown(int seatNum, String userId) {
        boolean flag = false;
        PlayerInfo player = allPlayers.get(userId);
        if (player != null && !inGamePlayersBySeatNum.containsKey(seatNum)
                && !inGamePlayersByPlayerId.containsKey(userId)) {
            removeNullSeat(seatNum);
            //玩家坐下重置状态
            player.sitDownAfterInit();
            player.setSeatNum(seatNum);
            player.setState(PlayerStateEnum.siteDown);
            inGamePlayersBySeatNum.put(seatNum, player);
            inGamePlayersByPlayerId.put(userId, player);
            allPlayers.forEach((k, v) -> {
                v.setStandUpTime(0L);
                log.debug("重置玩家standUpTime为0, userId->{}, tableId->{}, roomId->{}, gameId->{}, siteDownUserId->{}, playerState->{}",
                        v.getPlayerId(), tableId, roomId, playType, userId, v.getState());
            });
            flag = true;
            StoredObjManager.hset(RedisConst.TABLE_SEAT.getProfix() + getPlayType() + player.getRoomId() + player.getTableId(),
                    RedisConst.TABLE_SEAT.getField() + seatNum, "" + userId);
            //设置座位
            GameRoomTableSeatRelationModel gameRoomTableSeatRelationModel = StoredObjManager.getStoredObjInMap(
                    GameRoomTableSeatRelationModel.class, RedisConst.USER_TABLE_SEAT.getProfix(),
                    RedisConst.USER_TABLE_SEAT.getField() + player.getPlayerId());

            log.debug("gameRoomTableSeatRelationModel != null ---> " + (gameRoomTableSeatRelationModel != null));
            if (gameRoomTableSeatRelationModel != null) {
                gameRoomTableSeatRelationModel.setTableId(tableId);
                gameRoomTableSeatRelationModel.setSeat(seatNum);
                StoredObjManager.hset(RedisConst.USER_TABLE_SEAT.getProfix(), RedisConst.USER_TABLE_SEAT.getField() + player.getPlayerId(),
                        gameRoomTableSeatRelationModel);
            }
        }
        return flag;
    }

    /**
     * 如果执行就执行正确
     * 检查没有此玩家也为站起成功
     *
     * @param seatNum
     * @param userId
     * @param standUpType
     * @return
     */
    @Override
    public boolean standUp(Integer seatNum, String userId, String standUpType) {
        String guid = UUID.randomUUID().toString();
        boolean flag = false;
        //当牌局不是空闲或结算状态时，从游戏用户列表中移除用户，否则先不移除用户，等游戏结算进行结算时再移除
        PlayerInfo player = inGamePlayersBySeatNum.get(seatNum);
        log.debug("standUp() seatNum:{},userId:{},standUpType:{}", seatNum, userId, standUpType);
        if (player == null) {
            log.info("inGamePlayersBySeatNum is null. info->" + TableUtil.inGamePlayersBySeatNumToString(this));
        }
        StringBuilder sb = new StringBuilder();
        log.debug("tableInfo->" + TableUtil.toStringNormal(this));
        for (PlayerInfo playerInfo : allPlayers.values()) {
            sb.append("playerInfo:" + playerInfo.toString() + "," + System.getProperty("line.separator"));
        }
        log.debug(sb.toString());

        if (null != player) {
            //判断当前座位上的人，是否是需要站起的玩家。
            if (!player.getPlayerId().equals(userId)) {
                //TODO：没想好是否还需要清除某个userId对应的缓存数据
                log.error("standUp()。seatNum:{},userId->{}, seat's UserId->{}, tableInfo:{},inGamePlayerBySeatNum->{}," +
                                "allPlayers:{},reason:{}"
                        , seatNum, userId, player.getPlayerId(), TableUtil.toStringNormal(this), TableUtil.inGamePlayersBySeatNumToString(this),
                        TableUtil.toStringAllPlayers(this), standUpType);
                standUpPatch(userId);
                //flag = judageStandup(seatNum, player, userId);
                return flag;
            }
        }


        if (player != null) {
            inGamePlayersBySeatNum.remove(seatNum);
            inGamePlayersByPlayerId.remove(player.getPlayerId());
            inGamePlayers.remove(seatNum);
            addNullSeat(seatNum);
            PlayerInfo p = allPlayers.get(userId);
            p.setState(PlayerStateEnum.spectator);
            flag = true;
            TableLogUtil.standUp(FunctionIdHolder.Game_REQ_ApplyLeave, standUpType, player.getPlayerId(), getPlayType() + "", getRoomId(),
                    getTableId(), player.getPlayScoreStore(), TableUtil.inGamePlayersBySeatNumToString(this));

            String redisKey = RedisConst.TABLE_SEAT.getProfix() + getPlayType() + player.getRoomId() + player.getTableId();
            String redisField = RedisConst.TABLE_SEAT.getField() + player.getSeatNum();
            boolean isSuccDelRedis = StoredObjManager.hdel(redisKey, redisField);
            log.info("用户站起成功，从游戏用户列表中移除此用户。playerId->" + player.getPlayerId() + ", table->" + TableUtil.toStringNormal(this)+", isDelRedis->"+isSuccDelRedis+", redisKey->"+redisKey+", redisField->"+redisField);

            String con;
            JSONObject js = new JSONObject();
            js.put("gameId", getPlayType());
            js.put("roomId", player.getRoomId());
            js.put("tableId", player.getTableId());
            con = js.toString();
            log.debug("发给room的站起协议，con:" + con + ", table->" + TableUtil.toStringNormal(this));
            StoredObjManager.publish(con,
                    RedisChannel.STAND_UP_TABLE_CHANNEL.getChannelName());
            //设置座位
            GameRoomTableSeatRelationModel gameRoomTableSeatRelationModel = StoredObjManager.getStoredObjInMap(
                    GameRoomTableSeatRelationModel.class, RedisConst.USER_TABLE_SEAT.getProfix(),
                    RedisConst.USER_TABLE_SEAT.getField() + player.getPlayerId());
            if (gameRoomTableSeatRelationModel != null) {
                gameRoomTableSeatRelationModel.setSeat(0);
                StoredObjManager.hset(RedisConst.USER_TABLE_SEAT.getProfix(), RedisConst.USER_TABLE_SEAT.getField() + player.getPlayerId(),
                        gameRoomTableSeatRelationModel);
            }
        }

        if (inGamePlayersBySeatNum.size() == 0) {
            lastActionTime = 0;
        }
        if (!flag && player != null) {
            LoggerUtils.tableLog.error("stand up fail，standUpType:{},seatNum:{},userId:{},tableInfo:{},in AllPlayers:{}," +
                            "inGamePlayersBySeatNum 's playerInfo:{},inGamePlayersByPlayerId:{}", standUpType, seatNum, userId, TableUtil.toStringNormal(this),
                    allPlayers.get(userId) == null ? "NULL" : allPlayers.get(userId).playerToString()
                    , inGamePlayersBySeatNum.get(seatNum) == null ? "Null" : player.playerToString()
                    , inGamePlayersByPlayerId.get(userId) == null ? "Null" : inGamePlayersByPlayerId.get(userId).playerToString()
            );
            StoredObjManager.hsetnx(RedisConst.ALARM_CHECK.getProfix() + AlarmEnum.STAND_UP_FAILED.getType(),
                    RedisConst.ALARM_CHECK.getField() + AlarmEnum.STAND_UP_FAILED.getType(),
                    "gameId:" + getPlayType() + ",roomId:" + getRoomId() + ",tableId:" + getTableId() + ",userId:" + userId + "seatId:" + seatNum);
        }
        String res = StoredObjManager.hget(RedisConst.TABLE_USERS.getProfix() + this.getPlayType() + getRoomId() + getTableId(),
                RedisConst.TABLE_USERS.getField() + userId);
        if (StringUtils.isNotEmpty(res)) {
            StoredObjManager.hset(RedisConst.TABLE_USERS.getProfix() + this.getPlayType() + getRoomId() + getTableId(),
                    RedisConst.TABLE_USERS.getField() + userId, "" + PlayerStateEnum.spectator.getValue());
        }
        //flag = judageStandup(seatNum, player, userId);

        if (player != null && player.isOffLine() && flag) {
            LeaveTableLogic.getInstance().logic(player, this, null);
        }
        return flag;
    }

    /**
     * 根据数据来判定站起是否成功
     *
     * @param seatNum
     * @param player
     * @param userId
     * @return
     */
    private boolean judageStandup(int seatNum, PlayerInfo player, String userId) {
        if (player != null) {
            if (player.getPlayerId().equals(userId)) {
                log.error("judageStandUp()，seatNumPlayer's playerId equals userId ");
                return false;
            }
        }
        if (inGamePlayersByPlayerId.containsKey(userId)) {
            log.error("judageStandUp()，inGamePlayersByPlayerId contains userId ");
            return false;
        }
        PlayerInfo p = inGamePlayers.get(seatNum);
        if (p != null && p.getPlayerId().equals(userId)) {
            log.error("judageStandUp()，inGamePlayers's playerId equals userId ");
            return false;
        }
        return true;
    }

    /**
     * 站起补丁
     *
     * @param userId
     */
    public void standUpPatch(String userId) {
        inGamePlayersBySeatNum.forEach((k, v) -> {
            if (v.getPlayerId().equals(userId)) {
                inGamePlayersBySeatNum.remove(k);
                return;
            }
        });
        inGamePlayersByPlayerId.remove(userId);
    }

    /**
     * 离桌
     *
     * @param userId
     */
    public void returnLobby(String userId) {
        returnLobby(userId, true);
    }

    @Override
    public void returnLobby(String userId, boolean force) {
        log.debug("returnLobby()执行用户离桌操作，是否推送notice->"+force+", 删除用户列表中的用户，userId->" + userId + "，删除前列表人数->" + allPlayers.size()+", tableInfo->"+toString());
        PlayerInfo playerInfo = allPlayers.remove(userId);
        if (playerInfo != null) {
            log.debug("returnLobby()执行用户离桌操作，删除用户列表中的用户，userId->" + userId + "，删除后列表人数->" + allPlayers.size() + ", player.seatNum->" + playerInfo.getSeatNum() + ", tableInfo->" + toString());
        }
        if (playerInfo != null && playerInfo.getSeatNum() > 0) {
            PlayerInfo inGamePlayerInfo = inGamePlayersBySeatNum.get(playerInfo.getSeatNum());
            if (null != playerInfo && null != inGamePlayerInfo) {
                if (playerInfo.getPlayerId().equals(inGamePlayerInfo.getPlayerId())) {
                    inGamePlayersBySeatNum.remove(playerInfo.getSeatNum());
                    inGamePlayersByPlayerId.remove(playerInfo.getPlayerId());
                    addNullSeat(playerInfo.getSeatNum());//modify 2018-10-12 lyb
                }
            }
        }
        //设置座位
        Boolean isDel = StoredObjManager.hdel(RedisConst.USER_TABLE_SEAT.getProfix(), RedisConst.USER_TABLE_SEAT.getField() + userId);
        log.info("returnLobby()是否清除了Redis中的座位数据，idDel:{}, gameId:{},roomId:{},tableId:{},playerId:{},del key:{},del value:{}",
                isDel, getPlayType(), getRoomId(), getTableId(),
                userId, RedisConst.USER_TABLE_SEAT.getProfix(),
                RedisConst.USER_TABLE_SEAT.getField() + userId
        );

        if (force) {//强制广播
            JoloGame.JoloGame_TablePlay_OtherPlayerInfo.Builder otherPlayerInfo =
                    JoloGame_tablePlay_OtherPlayerInfoBuilder.getOtherPlayerInfo(playerInfo);
            User user = StoredObjManager.hget(RedisConst.USER_INFO.getProfix(), RedisConst.USER_INFO.getField() + userId, User.class);
            //广播离桌
            boardcastMessageSingle(userId, JoloGame.JoloGame_Notice2Client_leaveReq.newBuilder()
                            .setRoomId(getRoomId())
                            .setTableId(getTableId())
                            .setUserId(userId)
                            .setCurrStoreScore(user == null ? 0 : user.getMoney())
                            .setOtherPlayerInfo(otherPlayerInfo)
                            .setWinLoseScore(playerInfo == null ? 0 : playerInfo.getTotalWinLoseScore())
                            .build(),
                    FunctionIdHolder.Game_Notice2Client_leavel);
        }
    }

    @Override
    public PlayerInfo getPlayer(String playerId) {
        return allPlayers.get(playerId);
    }

    @Override
    public int giveCardCounts() {
        return CardConstent.HAND_CARDS;
    }

    //region 空座位管理
    public void addNullSeat(int seatNum) {
        if (inGamePlayersBySeatNum.containsKey(seatNum)) {
            return;
        }
        if (nullSeatList.contains(seatNum)) {
            log.info("addNullSeat error");
            return;
        }
        nullSeatList.add(seatNum);
    }

    private boolean removeNullSeat(int seatNum) {
        if (null != nullSeatList && nullSeatList.size() > 0) {
            return nullSeatList.remove(seatNum);
        }
        return false;
    }

    public synchronized int getNulSeatNum() {
        int seatNum = 0;
        try {
            Random random = new Random(System.currentTimeMillis());
            if (nullSeatList.size() > 0) {
                seatNum = (int) (nullSeatList.toArray())[random.nextInt(nullSeatList.size())];
            }
            if (null != nullSeatList && nullSeatList.size() > 0) {
                nullSeatList.remove(seatNum);
            }
        } catch (Exception ex) {
            log.error("nullSeatList size: " + nullSeatList.size() + "," + ex.getMessage(), ex);
        }
        if (seatNum == 0 && nullSeatList.size() > 0) {//modify lyb 2018-07-24 随机不到若本桌还有空位则给一个
            seatNum = nullSeatList.poll();
        }
        log.debug("seatNum:{},nullSeatList:{}", seatNum, nullSeatList.toArray());
        return seatNum;
    }

    @Override
    public void setGameId(String gameId) {
        setPlayType(Integer.parseInt(gameId));
    }

    /**
     * 设置庄位
     * 如果牌局未开始，那么在入座玩家中随机一个座位设置为庄位
     * 如果已经存在庄位，那么顺时针依次坐庄
     */
    @Override
    public void setDealer() {
        setLastActionTime(System.currentTimeMillis());
        log.debug("设置庄位开始, tableId->" + tableId + ". tableState->" + getTableStateEnum() + ", currDealerSeatNum->" + currDealerSeatNum);
        if (this.currDealerSeatNum > 0 && inGamePlayers.size() >= 2) { //如果已经存在庄位，那么顺时针依次坐庄
            //取出上一个坐庄的玩家对象，并修改用户是否坐庄的状态
            PlayerInfo preDealerPlayer = inGamePlayers.get(this.currDealerSeatNum);
            modifyIsDealer4Player(preDealerPlayer, 0);
            //计算下一个庄位的座位号
            int nextDealerSeatNum = currDealerSeatNum;
            while (true) {
                nextDealerSeatNum -= 1;
                if (nextDealerSeatNum < 1) {
                    nextDealerSeatNum = 5;
                }
                if (this.getInGamePlayers().containsKey(nextDealerSeatNum)) {
                    PlayerInfo nextDealerPlayer = this.getInGamePlayers().get(nextDealerSeatNum);
                    modifyIsDealer4Player(nextDealerPlayer, 1);
                    break; //找到下一个庄家，退出循环
                }
            }
        } else if (this.getTableStateEnum() == TableStateEnum.GAME_START && inGamePlayers.size() >= 2) {
            PlayerInfo[] arrPlayer = inGamePlayersBySeatNum.values().toArray(new PlayerInfo[inGamePlayersBySeatNum.size()]);
            Random random = new Random(System.currentTimeMillis());
            PlayerInfo playerInfo = arrPlayer[random.nextInt(arrPlayer.length)];
            modifyIsDealer4Player(playerInfo, 1);
        }
    }

    /**
     * 修改用户状态：是否庄位
     *
     * @param playerInfo
     * @param isDealer
     */
    private void modifyIsDealer4Player(PlayerInfo playerInfo, int isDealer) {
        if (playerInfo == null) {
            return;
        }
        playerInfo.setIsDealer(isDealer);
        if (isDealer == 1) {
            this.currDealerSeatNum = playerInfo.getSeatNum();
        }
    }

    @Override
    public List<Integer> getChangeCards() {
        return new ArrayList<>();
    }

    /**
     * 获得下一个行动的玩家
     *
     * @param preBetSeatNum
     * @return
     */
    @Override
    public PlayerInfo getNextBetPlayer(int preBetSeatNum, NextBetPlayerReasonEnum reasonEnum) {
        PlayerInfo nextBetPlayer = null;
        if (this.getInGamePlayers().size() <= GameConst.FINAL_PLAYER_NUM &&
                !reasonEnum.equals(NextBetPlayerReasonEnum.FOLD)&&
                reasonEnum.equals(NextBetPlayerReasonEnum.GAME_LOGIC_BET)) {
            log.info("getNextBetPlayer() inGamePlayers's size:{},reason:{}", this.getInGamePlayers().size(), reasonEnum.getReason());
            return null;
        }
        //计算下一个下注玩家的座位号
        int nextBetSeatNum = preBetSeatNum;
        while (true) {
            nextBetSeatNum -= 1;
            if (nextBetSeatNum < 1) {
                nextBetSeatNum = 5;
            }
            //再次找到当前位置，说明已无合适下一个行动者，返回null
            if (nextBetSeatNum == preBetSeatNum) {
                log.error("getNextBetPlayer() nextBetSeatNum == preBetSeatNum=={}", preBetSeatNum);
                return null;
            }
            //如果当前行动者的下家是庄家，则轮次加1
            if (nextBetSeatNum == this.firstBetSeatNum) {
                //达到盲牌次数上线则广播提示所有玩家看牌(前端发送看牌协议)
//                if (this.getCurrRoundId() == this.getRoomConfig().getMaxBlindRound() + 1) {
//                    NoticeBroadcastMessages.settlePrompt(this);
//                }
                //轮次上线时返回null，会进入结算
                if(this.currRoundId == this.getRoomConfig().getMaxBlindRound()){
                    return null;
                }
                this.currRoundId += 1;
            }
            if (this.getInGamePlayers().containsKey(nextBetSeatNum)) {
                nextBetPlayer = this.getInGamePlayers().get(nextBetSeatNum);
                if (nextBetPlayer.getState() != PlayerStateEnum.fold && nextBetPlayer.getState() != PlayerStateEnum.showfail) {
                    //找到了合适的下一个行动者，结束循环
                    break;
                }
            }
        }
        //将下一个行动玩家，设置为牌桌当前行动玩家
        if (null != nextBetPlayer) {
            setCurrActionPlayerId(nextBetPlayer.getPlayerId());
            setCurrActionSeatNum(nextBetPlayer.getSeatNum());
        }
        return nextBetPlayer;
    }

    /**
     * 获得下一个比牌玩家(专用于特殊比牌：轮数上限和孤注一掷)
     *
     * @param preBetSeatNum
     * @return
     */
    @Override
    public PlayerInfo getShowPlayer(int preBetSeatNum) {
        PlayerInfo nextBetPlayer = null;
        if (this.getInGamePlayers().size() <= GameConst.FINAL_PLAYER_NUM) {
            return null;
        }
        //计算下一个下注玩家的座位号
        int nextBetSeatNum = preBetSeatNum;
        while (true) {
            nextBetSeatNum -= 1;
            if (nextBetSeatNum < 1) {
                nextBetSeatNum = 5;
            }
            //再次找到当前位置，说明已无合适下一个行动者，返回null
            if (nextBetSeatNum == preBetSeatNum) {
                return null;
            }
            if (this.getInGamePlayers().containsKey(nextBetSeatNum)) {
                nextBetPlayer = this.getInGamePlayers().get(nextBetSeatNum);
                if (nextBetPlayer.getState() != PlayerStateEnum.fold && nextBetPlayer.getState() != PlayerStateEnum.showfail) {
                    //找到了合适的下一个行动者，结束循环
                    break;
                }
            }
        }
//        //将下一个行动玩家，设置为牌桌当前行动玩家
//        if (null != nextBetPlayer) {
//            setCurrActionPlayerId(nextBetPlayer.getPlayerId());
//            setCurrActionSeatNum(nextBetPlayer.getSeatNum());
//        }
        return nextBetPlayer;
    }

    @Override
    public PlayerInfo getWinnerByCompareCards(PlayerInfo player1, PlayerInfo player2) {
        if (null != player1.getHandCards() && (player1.getHandCards().length == 2 || player1.getHandCards().length == 3)
                && null != player2.getHandCards() && (player2.getHandCards().length == 2 || player2.getHandCards().length == 3)) {
            HandCardTypeModel player1Cards = player1.getHandCardTypeModel(this);
            HandCardTypeModel player2Cards = player2.getHandCardTypeModel(this);

            if(player1Cards.isStraightAAA && player2.isJoker235()){
                player2Cards.setJoker235(player2);
            }else if(player2Cards.isStraightAAA && player1.isJoker235()){
                player1Cards.setJoker235(player1);
            }
            //如果比过一次牌则把玩家可以变成235的标记去除
            player1.setJoker235(false);
            player2.setJoker235(false);

            if (null != player1Cards && null != player2Cards) {
                int result = player1Cards.compareTo(player2Cards);
                if (result < 0) { //player1 小于 player2
                    return player2;
                } else if (result > 0) { //player1 大于 player2
                    return player1;
                } else if (result == 0) { //牌力相同
                    //三张癞子是所有牌型中最大的
                    if (player1.getMixedCard() == 3) {
                        return player1;
                    } else if (player2.getMixedCard() == 3) {
                        return player2;
                    }
                    if (player1.getPlayerId().equals(this.getCurrActionPlayerId())) {
                        return player2;
                    } else {
                        return player1;
                    }
                }
            }
        }
        return null;
    }

    public double playerDataSettlement(PlayerInfo player) {
        return this.playerDataSettlement(player, true);
    }

    /**
     * 结算玩家数据
     *
     * @param player
     * @paran isSendNotice 是否在玩家站起后，发送Notice广播 add by gx 20181009 原因：牌局结算后，解散牌桌，站起每个玩家。为了方面客户端不处理站起Notice，因此控制可不发送站起Notice消息
     * @return
     */
    public double playerDataSettlement(PlayerInfo player, boolean isSendNotice) {
        log.info("结算玩家输赢数据， userId->{}, nickName->{}, getTotalWinLoseScore->{}, getPlayScoreStore->{}, takeInStore->{}, TableInfo->{}",
                player.getPlayerId(), player.getNickName(), player.getTotalWinLoseScore(), player.getPlayScoreStore(), player.getTotalTakeInScore(), this.toString());

        if (player.getPlayScoreStore() > 0) {
            player.setTotalWinLoseScore(player.getPlayScoreStore() - player.getTotalTakeInScore());
        }
        double currentMoney = MoneyService.getInstance().updateMoney(player.getPlayerId(), player.getPlayScoreStore());
        //发送日志
        User user = DBUtil.selectByPrimaryKey(player.getPlayerId());
        if (user != null) {
            LogService.OBJ.sendMoneyLog(user, user.getMoney(), currentMoney, user.getMoney() - currentMoney, LogReasons.CommonLogReason.GAME_SETTLE);
        }

        log.debug("plaery standup, playScore on table=>" + player.getPlayScoreStore() + ", userMoneyStore after standUp->" + currentMoney);

        player.setPlayScoreStore(0); //修改玩家桌内货币携带量为0
        player.setState(PlayerStateEnum.spectator);//设置玩家状态为旁观

        if (player.getIsBlind() != 1) {
            this.setSeeCardNum(this.getSeeCardNum() - 1);
        }

        this.getInGamePlayers().remove(player.getSeatNum()); //将玩家从游戏中移除

        PlayerInfo nextActionPlayer = null;

        // player.getPlayerId().equals(this.getCurrTargetSideShowPlayerId()) ||
        if (((this.getTableStateEnum() != TableStateEnum.SETTLE
                && player.getPlayerId().equals(this.getCurrActionPlayerId()))
                && this.getInGamePlayers().size() >= 2)) {
            nextActionPlayer = this.getNextBetPlayer(player.getSeatNum(), NextBetPlayerReasonEnum.GAME_LOGIC_PLAYER_DATA_SETTLEMENT);
            //停止牌桌内正在进行的倒计时
            TimerService.getInstance().delTimerTask(this.getRoomTableRelation());
            GameLogic.betTimer(this); //启动下一个玩家的下注计时器
        }

        //如果站起的人是已看牌玩家，则重新判断当前行动玩家是否可以sideshow
        setUpPlayerShowCard();

        if (nextActionPlayer != null) {
            nextActionPlayer.setState(PlayerStateEnum.beting);
            if (this.getInGamePlayers().size() == 2 && this.getSeeCardNum() == 2) {
                nextActionPlayer.setIsCanShow(1);
            }
        }

        try {
//            if (player.getState() != PlayerStateEnum.siteDown && this.getTableStateEnum() != TableStateEnum.IDEL) {
//                NoticeBroadcastMessages.applyFold(this, player, nextActionPlayer);
//            }
            //广播玩家站起消息
            log.info("玩家站起，isSendNotice->{}, userId->{}, nickName->{}, seatNum->{}, gameId->{}, roomId->{}, tableId->{}",
                    isSendNotice, player.getPlayerId(), player.getNickName(), player.getSeatNum(), this.getPlayType(), this.getRoomId(), this.getTableId());
            if(isSendNotice) {
                NoticeBroadcastMessages.playerStandUp(this, player.getPlayerId(), player.getSeatNum());
            }

            if (this.getInGamePlayersBySeatNum().size() < 2) {
                if (this.getTableStateEnum() == TableStateEnum.GAME_START) {
                    //停止牌桌内正在进行的倒计时
                    TimerService.getInstance().delTimerTask(this.getRoomTableRelation());
                    this.setTableStateEnum(TableStateEnum.IDEL);
                    setTableStatus();
                }
            }

            if (this.getInGamePlayers().size() < 2) {
                if (this.getTableStateEnum() == TableStateEnum.BET) {
                    //如果游戏中只有两个人以下，那么进入结算逻辑
                    GameLogic.settle(this);
                }
            }else if (this.getInGamePlayersBySeatNum().size() >= 2
                    && this.getTableStateEnum() != TableStateEnum.GAME_START
                    && this.getTableStateEnum() != TableStateEnum.SETTLE) {
                //修改桌子的状态
                this.setTableStateEnum(TableStateEnum.IDEL);
                setTableStatus();
                this.initTableStateAttribute();
                TableService.getInstance().playGame(this);//重新开始游戏
                return currentMoney;
            }

        } catch (Exception ex) {
            log.error("SendNotice ERROR：", ex);
        }
        return currentMoney;
    }

    @Override
    public void choosePlayType(boolean none) {

    }

    public ArrayList<String> getInGamePlayerIds(String exceptUserId) {
        ArrayList<String> arrayList = new ArrayList<>();

        for (PlayerInfo player : inGamePlayersByPlayerId.values()) {
            if (!player.getPlayerId().equals(exceptUserId)) {
                arrayList.add(player.getPlayerId());
            }
        }
        return arrayList;
    }

    /**
     * 推送消息(消息体相同时)
     */
    public void boardcastMessage(String tableId, MessageLite messageLite, int functionId) {
        List<String> list = new ArrayList<>();
        for (String playerId : allPlayers.keySet()) {
            PlayerInfo player = allPlayers.get(playerId);
            if (player != null) {
                if (!player.isOffLine()) {
                    list.add(player.getPlayerId());
                }
            }
        }

        log.debug("message functionId:" + functionId + "push player:" + list + ",getPlayType:" + getPlayType());
        NoticeRPCUtil.senMuliMsg(getPlayType(), tableId, list, functionId, messageLite);
    }

    /**
     * 推送消息,仅限于sideshow比牌
     */
    public void boardcastMessage(String tableId, String playerId1, String playerId2, MessageLite messageLite, int functionId) {
        List<String> list = new ArrayList<>();
        list.add(playerId1);
        //孤注一掷时第二个id会传null (ps:此处也可以用单独的消息推送，但这样改动最小)
        if(playerId2 != null) {
            list.add(playerId2);
        }
        NoticeRPCUtil.senMuliMsg(getPlayType(), tableId, list, functionId, messageLite);
    }

    /**
     * 推送消息(消息体相同时)
     */
    public void boardcastMessageSingle(String playerId, MessageLite messageLite, int functionId) {
        NoticeRPCUtil.sendSingMsg(getPlayType(), playerId, functionId, messageLite);
    }

    /**
     * 推送消息
     * @param tableId
     * @param toPlayer      自定义多个玩家
     * @param messageLite
     * @param functionId
     */
    public void boardcastMuliMessage(String tableId, List<String> toPlayer, MessageLite messageLite, int functionId){
        NoticeRPCUtil.senMuliMsg(getPlayType(), tableId, toPlayer, functionId, messageLite);
    }

    @Getter
    @Setter
    private long oldTimemillis = 0;

    private Map<String, Long> tmpTableUsers = new ConcurrentHashMap<>();

    /**
     * 超时离桌
     */
    public void timeOutLeaveTable() {
        long timeNow = System.currentTimeMillis();
        TableUtil.checkTablePlayer(this);
        try {
            boolean success = StoredObjManager.hsetnx(
                    RedisConst.HASH_SET_NX.getProfix() + "TABLE",
                    RedisConst.HASH_SET_NX.getField() + playType + getRoomId() + getTableId(),
                    "" + System.currentTimeMillis());
            if (!success) {
                return;
            }


            StringBuilder sb = new StringBuilder();
            long nowTime = System.currentTimeMillis();
            sb.append("timeOutLeaveTable()， nowTime: " + nowTime + ", gameId->" + playType + ", roomId:" + roomId + ". tableId:" + tableId + ", inGamePlayerCnt:" + getInGamePlayersBySeatNum().size());
            if (getInGamePlayersBySeatNum().size() == 1) {
                for (PlayerInfo player : getInGamePlayersBySeatNum().values()) {
                    if (player.getStandUpTime() == 0L) {
                        //sb.append(", playerId :" + player.getPlayerId());
                        log.debug("发现桌内只剩余一个玩家，给此玩家设置倒计时为当前时间。userId->{}, roomId-{}, tableId->{}, gameId->{}, nowTime->{}",
                                player.getPlayerId(), roomId, tableId, playType, nowTime);
                        player.setStandUpTime(System.currentTimeMillis());
                    }
                }
            } else if (getInGamePlayersBySeatNum().size() > 1) {
                //2分钟本桌没有操作则重新开始
                if (lastActionTime > 0 && nowTime - lastActionTime >= 2 * DateUtils.MILLIS_PER_MINUTE) {
                    log.error("牌桌重置，桌内两分钟没有行动。 tableInfo:{},players:{},inGameBySeatNum userInfo:{}", TableUtil.toStringNormal(this), TableUtil.toStringAllPlayers(this),
                            TableUtil.inGamePlayersBySeatNumToString(this));
                    lastActionTime = System.currentTimeMillis();
                    setTableStateEnum(TableStateEnum.IDEL);
                    setTableStatus();
                    TableService.getInstance().playGame(this);

                    StoredObjManager.set(RedisConst.GAME_STATUS_ACTIVE.getProfix() + Config.BIND_IP, "The game pauses ，" +
                            "playType:" + getPlayType() + ",roomId:" + roomId + ",tableId:" + tableId);
                }
            }

            //循环检查全部玩家，如果玩家已经站起，并且旁观时间
            allPlayers.forEach((k, v) -> {
                if (v.getStandUpTime() != 0L && nowTime - v.getStandUpTime() >= DateUtils.MILLIS_PER_MINUTE) {
                    //玩家达到离桌标准，强制离桌（标准：桌子上无任何玩家，并且玩家旁观等待时间超过配置的临界值）
                    try {
                        LeaveTableLogic.getInstance().logic(v, this, null);
                        v.setStandUpTime(0L);
                        sb.append(",强制玩家离开桌子 ,PlayerId:" + v.getPlayerId() + ", stanupTime->" + v.getStandUpTime() + ";" + System.getProperty("line.separator"));
                    } catch (Exception ex) {
                        log.error("强制玩家离开桌子，异常。playerId->{}, gameId->{}, roomId->{}, tableId->{}, msg->{}",
                                v.getPlayerId(), playType, roomId, tableId, ex.getMessage(), ex);
                    }
                }

                //桌子上无任何玩家时，强制给每个旁观者赋值离开桌子倒计时
                if (getInGamePlayersBySeatNum().size() == 0) {
                    if (v.getStandUpTime() == 0L) {
                        v.setStandUpTime(System.currentTimeMillis());
                    }
                }
            });
//        log.info(sb.toString());
            //加报警（玩家在桌上坐着确找不到相应数据）
            if (Config.GAME_ALARM) {
                TableUtil.tablePlayersMatchSeat(this, tmpTableUsers);
            }

        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
        } finally {
            StoredObjManager.hdel(RedisConst.HASH_SET_NX.getProfix() + "TABLE",
                    RedisConst.HASH_SET_NX.getField() + playType + getRoomId() + getTableId());

            if(System.currentTimeMillis() - timeNow >= 1000) {
                log.error("超时离桌检查，耗时 {} 毫秒,", System.currentTimeMillis() - timeNow);
            }
        }
    }

    public void initTableStateAttribute() {
        //初始化牌桌配置
        try {
            if (roomConfig != null) {
                this.baseBetScore = roomConfig.getFirstBaseBetScore() * 2;
                this.maxPotScore = roomConfig.getMaxPot();
            }
            if (this.changeCards != null) {
                this.changeCards.clear();//重置换牌信息
            }
            //XiaoKang 2018/3/21 todo
            this.currBaseBetScore = this.baseBetScore;
            this.currRoundId = 1; //重置轮次
            this.currBetSeqId = 0; //重置下注消息序号
            this.totalAlreadyBet = 0; //重置桌面总积分
            this.seeCardNum = 0; //重置桌面已看牌人数
            this.mixedCardId = 0; //重置桌子上的癞子牌ID
            this.inGamePlayers.clear(); //座位信息清除
            this.firstBetSeatNum = 0; //清空第一个下注值
            this.getAlreadyBet().clear();
            this.fullPressure.clear();
            this.playerCardsMap.clear();
        } catch (Exception e) {
            log.error("initTableStateAttribute ERROR, msg->{}", e.getMessage(), e);
        }
    }

    public int checkAndReturnBetSeqId() {
        return ++this.currBetSeqId;
    }

    public void setUpPlayerShowCard() {
        if (commonConfig == null) {
            log.error("setUpPlayerShowCard() Common Config is null ");
            return;
        }
        if (this.getTableStateEnum() == TableStateEnum.BET
                && !commonConfig.isChooseSideShowUser()) {
            for (PlayerInfo playerInfo : this.getInGamePlayers().values()) {
                playerInfo.setIsCanSideShow(0);
            }
            for (Integer in : this.getInGamePlayers().keySet()) {
                PlayerInfo targetPlayer = TableUtil.getPreBetPlayer(this, in);
                PlayerInfo playerInfo = this.getInGamePlayers().get(in);
                if (targetPlayer != null
                        && playerInfo != null
                        && targetPlayer.getIsBlind() != 1
                        && playerInfo.getIsBlind() != 1) {
                    playerInfo.setIsCanSideShow(1);
                }
            }
            PlayerInfo playerInfo = this.getPlayer(this.getCurrActionPlayerId());
            if (this.getInGamePlayers().size() == 2 && this.getSeeCardNum() == 2) {
                playerInfo.setIsCanShow(1);
            }
            if (playerInfo != null) {
                NoticeBroadcastMessages.currActionPlayerInfo(playerInfo, this);
            }
        } else if (this.getTableStateEnum() == TableStateEnum.BET
                && commonConfig.isChooseSideShowUser()) {
            if (this.getSeeCardNum() < 2) {
                for (PlayerInfo playerInfo : this.getInGamePlayers().values()) {
                    if (playerInfo.getIsBlind() != 1) {
                        playerInfo.setIsCanSideShow(0);
                    }
                }
            }
            PlayerInfo playerInfo = this.getPlayer(this.getCurrActionPlayerId());
            if (this.getInGamePlayers().size() == 2 && this.getSeeCardNum() == 2) {
                playerInfo.setIsCanShow(1);
            }
            if (playerInfo != null) {
                NoticeBroadcastMessages.currActionPlayerInfo(playerInfo, this);
            }
        }
    }

    @Override
    public int currPlayType() {
        return getPlayType();
    }

    public void accumulativeBet(String playerId, double in) {
        alreadyBet.put(playerId, alreadyBet.get(playerId) + in);
    }

    @Override
    public String toString() {
        return "Table{" +
                "roomId=" + roomId +
                ", tableId='" + tableId + '\'' +
                ", tableStateEnum=" + tableStateEnum +
                ", allPlayers=" + allPlayers.size() +
                ", inGamePlayers=" + inGamePlayersBySeatNum.size() +
                '}';
    }
    public void setTableStatus(){
        String redisKey = RedisConst.TABLE_INSTANCE.getProfix() + getPlayType() + getRoomId();
        String fieldKey = RedisConst.TABLE_INSTANCE.getField() + getTableId();
        RoomTableRelationModel ret = StoredObjManager.getStoredObjInMap(RoomTableRelationModel.class,
                redisKey, fieldKey);
        if(null != ret){
            ret.setTableStatus(getTableStateEnum().getValue());
            StoredObjManager.hset(RedisConst.TABLE_INSTANCE.getProfix() + getPlayType() + getRoomId(),
                    RedisConst.TABLE_INSTANCE.getField() + getTableId(), ret);
        }else{
            log.error("setTableStatus ERROR, ret from redis is null. redisKey->{}, fieldKey->{}", redisKey, fieldKey);
        }
    }

    /**
     * 发牌操作
     */
    public void giveCardToTable(){
        //发N手牌到牌桌上
        playerCardsMap.clear();
        int cards = this.giveCardCounts();
        if (playType == PlayTypeEnum.changecard.getGameId()){
            cards += 1;
        }

        //发牌给桌上玩家
        boolean hasRobot = false;
        for (Integer seat : inGamePlayers.keySet()){
            PlayerInfo player = inGamePlayers.get(seat);
            if (player == null){
                continue;
            }

            if (player.getRoleType() == RoleType.ROBOT){
                hasRobot = true;
            }

            //取牌
            int[] cardTmp = CardOfTableHolder.TakeCardOperationObj(currGameOrderId).hair_card(cards, player.getPlayerId());
            playerCardsMap.put(player.getPlayerId(), cardTmp);

            //changecard 发4张牌
            if (playType == PlayTypeEnum.changecard.getGameId()){
                int[] handcard = {cardTmp[0],cardTmp[1],cardTmp[2]};
                player.setHandCards(handcard);
                continue;
            }

            //其他局正常发牌
            player.setHandCards(cardTmp);
        }

        if (!hasRobot){
            //没有机器人、好了别操心了
            return;
        }

        //机器人赢牌判定
        if (RobotStrategyService.OBJ.curRobotStrategy(playType)){
            //操作一下
            log.debug("AI机器人换牌策略启动");
            RobotStrategyService.OBJ.robotChangeCard(this);
        }
    }

    /**
     * 玩家拿牌
     * @param playerId
     * @return
     */
    public int[] getHandCard(String playerId){
        for (int seat : inGamePlayers.keySet()){
            PlayerInfo player = inGamePlayers.get(seat);
            if (!player.getPlayerId().equals(playerId)){
                continue;
            }
            if (player.getHandCards() == null){
                continue;
            }
            return player.getHandCards();
        }

        //无牌重新发牌
        if (playerCardsMap == null || playerCardsMap.size() == 0){
            this.giveCardToTable();
        }

        //如果玩家在游戏中才发牌
        if (!playerCardsMap.containsKey(playerId)){
            return null;
        }

        int[] cardTmp = playerCardsMap.get(playerId);

        //换牌开局发四张牌, 三张手牌 一张换牌用
        if (playType == PlayTypeEnum.changecard.getGameId()){
            int[] playCardTmp = {cardTmp[0],cardTmp[1],cardTmp[2]};
            return playCardTmp;
        }

        return cardTmp;
    }

    /**
     * 玩家换牌
     * @param playerId
     * @return
     */
    public int[] getChangeCard(String playerId){
        //容错牌
        int[] cardId = CardOfTableHolder.TakeCardOperationObj(currGameOrderId).hair_card(1, null);
        //无牌
        if (playerCardsMap == null || playerCardsMap.size() == 0){
            return cardId;
        }

        //如果玩家不在游戏
        if (!playerCardsMap.containsKey(playerId)){
            return cardId;
        }

        int[] cardTmp = playerCardsMap.get(playerId);
        //换牌开局发四张牌, 三张手牌 一张换牌用
        if (playType == PlayTypeEnum.changecard.getGameId()){
            int[] playCardTmp = {cardTmp[3]};
            return playCardTmp;
        }

        return cardId;
    }
}
